extern crate proc_macro;
extern crate syn;
extern crate quote;
extern crate serde;

use crate::proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{
    parse_macro_input, DeriveInput, Data, Fields, Field, PathArguments, Type, GenericArgument, Meta, NestedMeta, Lit
};

///
/// 将结构体数据转为String类型 如：`aa|bb|cc|[aa,bb]|2020-12-12|`
/// vector类型默认转换为`format("{:?}", vec)`
/// NaiveDateTime类型默认转换为'%Y%m%d%H%M%S%3f'，目前仅支持NaiveDateTime时间类型，格式不能写为"chrono::NaiveDateTime"
/// 其余类型转为to_string()
/// Option类型会自动unwrap(), None转换为空字符串""
/// 使用方式
/// ```
/// #[derive(StringFormat, Debug, serde::Deserialize)]
/// struct Hong {
///     #[field(skip)]
///     a: i32,
///     b: i32,
///     c: Vec<i32>,
///     #[field(date_format = "%Y-%m-%d")]
///     d: NaiveDateTime,
///     e: String,
///     f: Option<Vec<NaiveDateTime>>,
///     g: Option<i32>,
///     h: Option<NaiveDateTime>,
/// }
/// 
/// let dt: DateTime<Local> = Local::now();
/// let f = Some(vec![dt.naive_local()]);
/// let h = Hong { a: 11, b: 10, c: vec![1], d: dt.naive_local(), e: "aa".into(), f, g: None, h: None};
/// 
/// assert_eq!("10|[1]|2021-08-27|aa|[2021-08-27T16:04:30.374789]||", h.to_string());
/// 
/// #[derive(StringFormat, Debug, serde::Deserialize)]
/// struct Wei {
///     a: String,
///     b: String,
/// }
/// let s = "Hello|World!";
/// // new_with_string目前支持字段全部为string的类型
/// let w = Wei::new_with_string(s.to_string());
/// assert_eq!("Wei { a: "Hello", b: "World!" }", format!("{:?}", w));
/// ```
#[proc_macro_derive(StringFormat, attributes(field))]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    to_string(&ast)
}

const DATE_TIME_FORMAT: &str = "%Y%m%d%H%M%S%3f";
const SPLIT_CHAR: &str = "|";

fn to_string(ast: &DeriveInput) -> TokenStream {
    let str = match ast.data {
        Data::Struct(ref data) => {
            match data.fields {
                // 有名字段
                Fields::Named(ref fields) => {
                    let recurse = fields.named.iter()
                        .filter(|f| {
                            filter_fields(f)
                        }).map(|f| {
                        let name = &f.ident;
                        let ty = &f.ty;
                        match ty {
                            Type::Path(typath) => {
                                let date_format = get_date_format(f).unwrap_or(DATE_TIME_FORMAT.into());

                                let ty = typath.path.segments.iter().next().unwrap();
                                let type_name = ty.ident.to_string();
                                let generic_arg = if type_name == "Option" {
                                    match &ty.arguments {
                                        PathArguments::AngleBracketed(params) => {
                                            get_generic_type_name(params.args.iter().next().unwrap())
                                        }
                                        _ => "".into()
                                    }
                                } else {
                                    "".into()
                                };

                                match type_name.as_str() {
                                    "Vec" => {
                                        quote_spanned! {f.span()=>
                                            serde_json::to_string(&self.#name).unwrap().as_str()
                                        }
                                    },
                                    "NaiveDateTime" => {
                                        quote_spanned! {f.span()=>
                                            &self.#name.format(#date_format).to_string().as_str()
                                        }
                                    },
                                    "Option" => {
                                        match generic_arg.as_str() {
                                            "Vec" => {
                                                quote_spanned! { f.span()=>
                                                    format!("{:?}", self.#name.as_ref().unwrap_or(&Vec::new())).as_str()
                                                }
                                            },
                                            "NaiveDateTime" => {
                                                quote_spanned! { f.span()=>
                                                    format!("{}", &self.#name.map_or("".to_owned(), |x| x.format(#date_format).to_string())).as_str()
                                                }
                                            },
                                            _ => {
                                                quote_spanned! { f.span()=>
                                                    format!("{}", &self.#name.as_ref().map_or("".to_owned(), |x| x.to_string())).as_str()
                                                }
                                            }
                                        }
                                    }
                                    _ => quote_spanned! { f.span() => &self.#name.to_string() }
                                }
                            },
                            _ => panic!("Field type must be a Type::Path")
                        }
                    });
                    quote! {
                        // vec!["", self.x, self.y, self.z][1..].join("｜")，跳过第一个空字符串
                        vec!["" #(, #recurse)*][1..].join(#SPLIT_CHAR)
                    }
                }
                _ => panic!("Field must be named")
            }
        },
        _ => panic!("FieldName can only be derived for structures"),
    };

    let fields = match ast.data {
        Data::Struct(ref data) => {
            match data.fields {
                // 有名字段
                Fields::Named(ref fields) => {
                    let recurse = fields.named.iter()
                        .filter(|f| {
                            filter_fields(f)
                        }).map(|f| {
                        let name = &f.ident;
                        quote! { stringify!(#name) }
                    });
                    quote! {
                        // vec!["", self.x, self.y, self.z][1..].join("｜")，跳过第一个空字符串
                        vec!["" #(, #recurse)*][1..].join(#SPLIT_CHAR)
                    }
                }
                _ => panic!("Field must be named")
            }
        },
        _ => panic!("FieldName can only be derived for structures"),
    };

    let name = &ast.ident;
    let gen = quote! {
        impl #name {
            fn to_string(&self) -> String {
                println!("{}", #fields);
                #str
            }

            // 只支持字段全部为String类型的struct
            fn new_with_string(vv: String) -> #name {
                let mut map: HashMap<String, String> = HashMap::new();
                let mut i = 0;
                let fields: Vec<String> = #fields.split(#SPLIT_CHAR).map(|x| x.to_string()).collect();
                let strs = "{\"a\": \"hello\", \"b\": \"[world,abc,dd]\"}";
                let mut ss = String::from("{");
                for v in vv.split(#SPLIT_CHAR) {
                    ss.push_str("\"");
                    ss.push_str(&fields[i]);
                    ss.push_str("\":\"");
                    ss.push_str(v);
                    ss.push_str("\"");
                    i += 1;
                    if i < fields.len() {
                        ss.push_str(",");
                    }
                }
                ss.push_str("}");
                let r: #name = serde_json::from_str(&ss).unwrap();
                r
            }
        }
    };

    TokenStream::from(gen)
}

fn get_generic_type_name(generic_arg: &GenericArgument) -> String {
    match generic_arg {
        GenericArgument::Type(ty) => {
            match ty {
                Type::Path(typath) => {
                    typath.path.segments.iter().next().unwrap().ident.to_string()
                },
                _ => "".into()
            }
        }
        _ => panic!("TODO: error handling"),
    }
}

fn get_date_format(f: &Field) -> Option<String> {
    if f.attrs.len() == 0 {
        None
    } else {
        f.attrs.iter().find_map(|attr| {
            if attr.path.is_ident("field") {
                match attr.parse_meta().unwrap() {
                    Meta::NameValue(ref nv) => {
                        println!("{:?}", nv);
                        None
                    },
                    Meta::List(ref list) => {
                        match list.nested.first().unwrap() {
                            NestedMeta::Meta(Meta::NameValue(nv)) => {
                                if nv.path.is_ident("date_format") {
                                    match nv.lit {
                                        Lit::Str(ref s) => {
                                            Some(s.value())
                                        },
                                        _ => None
                                    }
                                } else {
                                    None
                                }
                            },
                            _ => None
                        }
                    },
                    _ => None
                }
            } else {
                None
            }
        })
    }
}

fn filter_fields(f: &Field) -> bool {
    if f.attrs.len() == 0 {
        true
    } else {
        f.attrs.iter().any(|attr| {
            if attr.path.is_ident("field") {
                match attr.parse_meta().unwrap() {
                    Meta::List(ref list) => {
                        match list.nested.first().unwrap() {
                            NestedMeta::Meta(Meta::Path(path)) => {
                                !path.is_ident("skip")
                            },
                            _ => true
                        }
                    },
                    _ => true
                }
            } else {
                true
            }
        })
    }
}